/**
 * 
 */
package com.huxi.lang.type;

import static com.huxi.lang.utils.BuilderUtils.mapof;

import java.lang.invoke.MethodHandle;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.ToIntFunction;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.reflect.MethodUtils;

import com.huxi.lang.exception.ValueParseException;
import com.huxi.lang.object.support.EnumBuilder;

/**
 * 类型工具类
 * 
 * @author jian.wu
 * @since 1.0
 * @version 1.0
 */
public abstract class VTypeUtils {

	/**
	 * 字符串是否为空白
	 * 
	 * @param s 字符串
	 * @return 非空值、空白串返回{@code true}，否则返回{@code false}。
	 */
	public static boolean isBlank(final CharSequence s) {
		if (s != null) {
			for (int i = 0; i < s.length(); i++) {
				if (!Character.isWhitespace(s.charAt(i))) {
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * 参数值是否为空白
	 * 
	 * @param v 参数值
	 * @return 非空值、空白串返回{@code true}，否则返回{@code false}。
	 */
	public static boolean isBlank(final Object v) {
		if (v instanceof CharSequence) {
			CharSequence s = (CharSequence) v;
			for (int i = 0; i < s.length(); i++) {
				if (!Character.isWhitespace(s.charAt(i))) {
					return false;
				}
			}
			return true;
		}
		return v == null ? true : false;
	}

	/**
	 * 字符串是否为空
	 * 
	 * @param s 字符串
	 * @return 空值或空串返回{@code true}，否则返回{@code false}。
	 */
	public static boolean isEmpty(final CharSequence s) {
		return s == null || s.length() == 0;
	}

	/**
	 * 参数值是否为空
	 * 
	 * @param v 参数值
	 * @return 非空值、空串、空数组、空列表、空哈希返回{@code true}，否则返回{@code false}。
	 */
	@SuppressWarnings("rawtypes")
	public static boolean isEmpty(final Object v) {
		if (v instanceof CharSequence) {
			return ((CharSequence) v).length() == 0;
		}
		if (v instanceof Collection) {
			return ((Collection) v).size() == 0;
		}
		if (v instanceof Map) {
			return ((Map) v).size() == 0;
		}
		if (v.getClass().isArray()) {
			return Array.getLength(v) == 0;
		}
		return v == null ? true : false;
	}

	/**
	 * 获取参数值长度
	 * 
	 * @param v 参数值
	 * @return 非空值、字符串、数组、列表、哈希返回-1，否则返回对应的长度。
	 */
	@SuppressWarnings("rawtypes")
	public static int getLength(final Object v) {
		if (v instanceof CharSequence) {
			return ((CharSequence) v).length();
		}
		if (v instanceof Map) {
			return ((Map) v).size();
		}
		if (v instanceof Collection) {
			return ((Collection) v).size();
		}
		if (v.getClass().isArray()) {
			return Array.getLength(v);
		}
		return v == null ? 0 : -1;
	}

	/**
	 * 比较参数值与零的大小
	 * 
	 * @param n 参数值，非空。
	 * @return 参数值小于零返回-1，等于零返回0，大于零返回1。
	 */
	public static int compareToZero(Number n) {
		if (n instanceof Double || n instanceof Float) {
			return Double.compare(n.doubleValue(), 0);
		} else if (n instanceof BigInteger) {
			return ((BigInteger) n).compareTo(BigInteger.ZERO);
		} else if (n instanceof BigDecimal) {
			return ((BigDecimal) n).compareTo(BigDecimal.ZERO);
		} else {
			return Long.compare(n.longValue(), 0);
		}
	}

	/**
	 * 两数值比较大小
	 * 
	 * @param n1 数值1，非空。
	 * @param n2 数值2，非空。
	 * @return 数值1小于数值2返回-1，数值1等于数值2返回0，否则返回1.
	 * @throws NullPointerException 非空参数为空时。
	 */
	public static int compare(Number n1, Number n2) {
		if (n1 instanceof Long || n1 instanceof Integer) {
			if (n2 instanceof Long || n2 instanceof Integer) {
				return Long.compare(n1.longValue(), n2.longValue());
			} else if (n2 instanceof Double || n2 instanceof Float) {
				return Double.compare(n1.doubleValue(), n2.doubleValue());
			} else if (n2 instanceof BigInteger) {
				return (BigInteger.valueOf(n1.longValue())).compareTo((BigInteger) n2);
			} else if (n2 instanceof BigDecimal) {
				return (BigDecimal.valueOf(n1.longValue())).compareTo(((BigDecimal) n2));
			} else {
				return Long.compare(n1.longValue(), n2.longValue());
			}
		} else if (n1 instanceof Double || n1 instanceof Float) {
			if (n2 instanceof Double || n2 instanceof Float || n2 instanceof Long || n2 instanceof Integer) {
				return Double.compare(n1.doubleValue(), n2.doubleValue());
			} else if (n2 instanceof BigInteger) {
				return (BigDecimal.valueOf(n1.doubleValue())).compareTo(new BigDecimal((BigInteger) n2));
			} else if (n2 instanceof BigDecimal) {
				return (BigDecimal.valueOf(n1.doubleValue())).compareTo(((BigDecimal) n2));
			} else {
				return Double.compare(n1.doubleValue(), n2.doubleValue());
			}
		} else if (n1 instanceof BigInteger) {
			if (n2 instanceof BigInteger) {
				return ((BigInteger) n1).compareTo((BigInteger) n2);
			} else if (n2 instanceof BigDecimal) {
				return (new BigDecimal((BigInteger) n1)).compareTo(((BigDecimal) n2));
			} else if (n2 instanceof Double || n2 instanceof Float) {
				return (new BigDecimal((BigInteger) n1)).compareTo(BigDecimal.valueOf(n2.doubleValue()));
			} else {
				return ((BigInteger) n1).compareTo(BigInteger.valueOf(n2.longValue()));
			}
		} else if (n1 instanceof BigDecimal) {
			if (n2 instanceof BigDecimal) {
				return ((BigDecimal) n1).compareTo((BigDecimal) n2);
			} else if (n2 instanceof BigInteger) {
				return ((BigDecimal) n1).compareTo((new BigDecimal((BigInteger) n2)));
			} else if (n2 instanceof Double || n2 instanceof Float) {
				return ((BigDecimal) n2).compareTo(BigDecimal.valueOf(n2.doubleValue()));
			} else {
				return ((BigDecimal) n1).compareTo(BigDecimal.valueOf(n2.longValue()));
			}
		} else {
			if (n2 instanceof Double || n2 instanceof Float) {
				return Double.compare(n1.doubleValue(), n2.doubleValue());
			} else if (n2 instanceof BigInteger) {
				return (BigInteger.valueOf(n1.longValue())).compareTo((BigInteger) n2);
			} else if (n2 instanceof BigDecimal) {
				return (BigDecimal.valueOf(n1.longValue())).compareTo(((BigDecimal) n2));
			} else {
				return Long.compare(n1.longValue(), n2.longValue());
			}
		}
	}

	/**
	 * 文本解析为数值
	 * 
	 * @param s 文本，非空。
	 * @return 数值，非空。
	 * @throws NumberFormatException 解析失败
	 */
	public static Number parseNumber(String s) throws NumberFormatException {
		if (s.indexOf('.') != -1) {
			if (s.length() < 18) {
				return Double.valueOf(s);
			}
			return new BigDecimal(s);
		}
		if (s.length() < 19) {
			return Long.valueOf(s);
		}
		BigInteger bi = new BigInteger(s);
		try {
			return bi.longValueExact();
		} catch (Exception e) {
			return bi;
		}
	}

	/**
	 * 遍历目标类实现的所有接口
	 * 
	 * @param clazz    目标类，非空。
	 * @param consumer 遍历，非空；返回非零值则提前中断遍历。
	 */
	public static void traversalInterface(Class<?> clazz, ToIntFunction<Class<?>> consumer) {
		Objects.requireNonNull(clazz);
		Objects.requireNonNull(consumer);
		if (clazz.isInterface()) {
			if (consumer.applyAsInt(clazz) != 0) {
				return;
			}
		}
		traversal2Interface(clazz.getInterfaces(), consumer);
	}

	private static boolean traversal2Interface(Class<?>[] uclasses, ToIntFunction<Class<?>> consumer) {
		for (Class<?> uclazz : uclasses) {
			if (consumer.applyAsInt(uclazz) != 0) {
				return true;
			}
			if (traversal2Interface(uclazz.getInterfaces(), consumer)) {
				return true;
			}
		}
		return false;
	}

	public static final MethodHandle[] EMPTY_ARRAY_METHODHANDLE = {};
	public static final Class<?>[] ORDERED_PRIMITIVE_TYPES = { Byte.TYPE, Short.TYPE, Character.TYPE, Integer.TYPE,
			Long.TYPE, Float.TYPE, Double.TYPE };

	public static final List<Class<?>> PRIMITIVE_INTEGER = Arrays.asList(Byte.TYPE, Short.TYPE, Character.TYPE,
			Integer.TYPE, Long.TYPE);
	public static final List<Class<?>> PRIMITIVE_DECIMAL = Arrays.asList(Double.TYPE, Float.TYPE);

	public static final List<Class<?>> PRIMITIVE_NUMBER = Arrays.asList(int.class, long.class, double.class,
			float.class, short.class, byte.class);

	public static final Map<Class<?>, Class<?>> PRIMITIVE_WRAP = Collections
			.unmodifiableMap(mapof(Void.TYPE, Void.class, Boolean.TYPE, Boolean.class, Byte.TYPE, Byte.class,
					Short.TYPE, Short.class, Character.TYPE, Character.class, Integer.TYPE, Integer.class, Long.TYPE,
					Long.class, Double.TYPE, Double.class, Float.TYPE, Float.class));

	public static final Map<Class<?>, Class<?>> PRIMITIVE_UNWRAP = Collections.unmodifiableMap(mapof(PRIMITIVE_WRAP));

	@SuppressWarnings("rawtypes")
	public static final ConcurrentMap<Class, EnumBuilder> enumClassBuilders = new ConcurrentHashMap<>();
	private static final Object enumClassLocked = new Object();

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static <T extends Enum<T>> EnumBuilder<T> getEnumBuilder(Class<T> enumType) {
		Objects.requireNonNull(enumType);
		EnumBuilder b = enumClassBuilders.get(enumType);
		if (b == null) {
			synchronized (enumClassLocked) {
				b = EnumBuilder.of(enumType);
				EnumBuilder o = enumClassBuilders.putIfAbsent(enumType, b);
				if (o != null) {
					b = o;
				}
			}
		}
		return b;
	}

	public static <T extends Enum<T>> T enumOf(Class<T> enumType, int ordinal) throws ValueParseException {
		return (T) getEnumBuilder(enumType).valueOf(ordinal);
	}

	public static <T extends Enum<T>> T enumOf(Class<T> enumType, int ordinal, boolean custom)
			throws ValueParseException {
		return (T) getEnumBuilder(enumType).valueOf(ordinal, custom);
	}

	public static <T extends Enum<T>> T enumOf(Class<T> enumType, String value) throws ValueParseException {
		return (T) getEnumBuilder(enumType).valueOf(value);
	}

	public static <T extends Enum<T>> T enumOf(Class<T> enumType, String value, boolean custom)
			throws ValueParseException {
		return (T) getEnumBuilder(enumType).valueOf(value, custom);
	}

	public static <T extends Enum<T>> T enumParse(Class<T> enumType, String value) throws ValueParseException {
		return (T) getEnumBuilder(enumType).parse(value);
	}

	public static <T extends Enum<T>> T enumParse(Class<T> enumType, String value, boolean custom)
			throws ValueParseException {
		return (T) getEnumBuilder(enumType).parse(value, custom);
	}

	public static boolean isAssignable(Class<?> cls, final Class<?> toClass, final boolean autoboxing) {
		return ClassUtils.isAssignable(cls, toClass, autoboxing);
	}

	public static boolean isAssignable(Class<?>[] classArray, Class<?>[] toClassArray, final boolean autoboxing) {
		return ClassUtils.isAssignable(classArray, toClassArray, autoboxing);
	}

	public static Method getMatchingMethod(final Class<?> cls, final String methodName,
			final Class<?>... parameterTypes) {
		return MethodUtils.getMatchingMethod(cls, methodName, parameterTypes);
	}

	@SuppressWarnings("deprecation")
	public static Object invokeMethod(final Object object, final boolean forceAccess, final Method method,
			Object[] args, Class<?>[] parameterTypes) throws IllegalAccessException, InvocationTargetException {
		parameterTypes = ArrayUtils.nullToEmpty(parameterTypes);

		args = ArrayUtils.nullToEmpty(args);

		if (forceAccess) {
			if (method != null && !method.isAccessible()) {
				method.setAccessible(true);
			}
		}
		args = toVarArgs(method, args);
		if (Modifier.isStatic(method.getModifiers())) {
			return method.invoke(null, args);
		} else {
			return method.invoke(object, args);
		}
	}

	private static Object[] toVarArgs(final Method method, Object[] args) {
		if (method.isVarArgs()) {
			final Class<?>[] methodParameterTypes = method.getParameterTypes();
			args = getVarArgs(args, methodParameterTypes);
		}
		return args;
	}

	static Object[] getVarArgs(final Object[] args, final Class<?>[] methodParameterTypes) {
		if (args.length == methodParameterTypes.length && (args[args.length - 1] == null
				|| args[args.length - 1].getClass().equals(methodParameterTypes[methodParameterTypes.length - 1]))) {
			// The args array is already in the canonical form for the method.
			return args;
		}

		// Construct a new array matching the method's declared parameter types.
		final Object[] newArgs = new Object[methodParameterTypes.length];

		// Copy the normal (non-varargs) parameters
		System.arraycopy(args, 0, newArgs, 0, methodParameterTypes.length - 1);

		// Construct a new array for the variadic parameters
		final Class<?> varArgComponentType = methodParameterTypes[methodParameterTypes.length - 1].getComponentType();
		final int varArgLength = args.length - methodParameterTypes.length + 1;

		Object varArgsArray = Array.newInstance(ClassUtils.primitiveToWrapper(varArgComponentType), varArgLength);
		// Copy the variadic arguments into the varargs array.
		System.arraycopy(args, methodParameterTypes.length - 1, varArgsArray, 0, varArgLength);

		if (varArgComponentType.isPrimitive()) {
			// unbox from wrapper type to primitive type
			varArgsArray = ArrayUtils.toPrimitive(varArgsArray);
		}

		// Store the varargs array in the last position of the array to return
		newArgs[methodParameterTypes.length - 1] = varArgsArray;

		// Return the canonical varargs array.
		return newArgs;
	}

}
